package ru.paswd.marketprice.distance.calculator.compare;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.stereotype.Component;
import ru.paswd.marketprice.distance.calculator.DistanceCalculator;
import ru.paswd.marketprice.distance.calculator.impl.PlaneDistanceCalculator;
import ru.paswd.marketprice.distance.calculator.impl.SphericalDistanceCalculator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
Предполагается, что реализация Spherical точнее, чем Plane
Данный модуль создан для подтверждения или опровержения данной гипотезы
 */
@Component
@Slf4j
public class DistanceCalculatorComparator {

    private static final int MAX_DIFF = 20;

    private final DistanceCalculator spherical = new SphericalDistanceCalculator();
    private final DistanceCalculator plane = new PlaneDistanceCalculator();

    public void compare() {

        List<DataSet> data = getIdealData();
        List<Triple<Integer, Integer, Integer>> results = new ArrayList<>();

        data.forEach(s -> results.add(calculateResultSet(s)));

        Counter counter = new Counter();
        results.forEach(r -> counter.increment(getCounterType(r)));

        log.info("Comparison result\n" +
                "Total cases: {}\n" +
                "Spherical is more correct in {} cases\n" +
                "Plane is more correct in {} cases\n" +
                "Results are equal in {} cases",
                counter.getTotal(),
                counter.getSpherical(),
                counter.getPlane(),
                counter.getEqual());
    }

    private Triple<Integer, Integer, Integer> calculateResultSet(DataSet dataSet) {
        int idealDistance = dataSet.getDistance();
        int sphericalDistance = spherical.getDistance(
                dataSet.getFirst().getLat(), dataSet.getFirst().getLng(),
                dataSet.getSecond().getLat(), dataSet.getSecond().getLng());

        int planeDistance = plane.getDistance(
                dataSet.getFirst().getLat(), dataSet.getFirst().getLng(),
                dataSet.getSecond().getLat(), dataSet.getSecond().getLng());

        log.info(
                "Calculation step\n" +
                        "First point: [{}, {}]\n" +
                        "Second point: [{}, {}]\n" +
                        "Ideal distance: {}m\n" +
                        "Spherical distance: {}m\n" +
                        "Plane distance: {}m",
                dataSet.getFirst().getLat(), dataSet.getFirst().getLng(),
                dataSet.getSecond().getLat(), dataSet.getSecond().getLng(),
                idealDistance,
                sphericalDistance,
                planeDistance);

        return new ImmutableTriple<>(idealDistance, sphericalDistance, planeDistance);
    }

    private CounterType getCounterType(Triple<Integer, Integer, Integer> resultSet) {
        int idealDistance = resultSet.getLeft();
        int sphericalDistance = resultSet.getMiddle();
        int planeDistance = resultSet.getRight();

        int diffSpherical = Math.abs(sphericalDistance - idealDistance);
        int diffPlane = Math.abs(planeDistance - idealDistance);

        if (Math.abs(diffSpherical - diffPlane) < MAX_DIFF) {
            return CounterType.EQUAL;
        }

        if (diffSpherical < diffPlane) {
            return CounterType.SPHERICAL;
        } else {
            return CounterType.PLANE;
        }
    }

    private static List<DataSet> getIdealData() {
        return Arrays.asList(
                new DataSet(
                        new Point(55.765084, 37.369305),
                        new Point(55.761197, 37.842647),
                        29700),
                new DataSet(
                        new Point(55.911153, 37.577111),
                        new Point(55.579674, 37.578060),
                        36900),
                new DataSet(
                        new Point(55.812875, 37.510598),
                        new Point(55.806306, 37.510327),
                        731),
                new DataSet(
                        new Point(55.806295, 37.510308),
                        new Point(55.806278, 37.517573),
                        455),
                new DataSet(
                        new Point(55.806295, 37.510308),
                        new Point(55.803749, 37.513679),
                        355)
        );
    }

    @RequiredArgsConstructor
    private static class Point {
        @Getter
        private final double lat;

        @Getter
        private final double lng;
    }

    @RequiredArgsConstructor
    private static class DataSet {
        @Getter
        private final Point first;

        @Getter
        private final Point second;

        @Getter
        private final int distance;
    }

    private static class Counter {
        @Getter
        private int total = 0;

        @Getter
        private int spherical = 0;

        @Getter
        private int plane = 0;

        @Getter
        private int equal = 0;

        synchronized public void increment(CounterType type) {
            switch (type) {
                case SPHERICAL:
                    spherical++;
                    break;

                case PLANE:
                    plane++;
                    break;

                case EQUAL:
                    equal++;
                    break;

                default:
                    break;
            }

            total++;
        }
    }

    private enum CounterType {
        SPHERICAL,
        PLANE,
        EQUAL
    }
}
